//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <limits.h>
#include "ini.h"
#include "config.h"

VOTER_WORKING_MODE   g_Voter_Working_Mode;

static NodeWhiteList g_NodeWhiteList[MAX_SIZE_NodeWhiteList];

/**
 * If the whitelist mechanism works, the number of whitelists must be
 * set correctly.
 */
// int g_numNodeWhiteList = sizeof(g_NodeWhiteList) / sizeof(g_NodeWhiteList[0]);
int g_numNodeWhiteList = 0;
NodeWhiteList *g_pNodeWhiteList = &g_NodeWhiteList[0];

// output config
static NodeWhiteList g_OutputNodeList[MAX_SIZE_OUTPUT_NodeList];
int g_numOutputNodeList = 0;
NodeWhiteList *g_pOutputNodeList = &g_OutputNodeList[0];

char *strZcpy(char *dest, const char *src, size_t maxlen)
{
    maxlen--;
    dest[maxlen] = '\0';
    return strncpy(dest, src, maxlen);
}

int GetHostname(char *buf, int bufSize)
{
    FILE *fp = popen("hostname", "r");
    if (NULL == fp) {
        perror("popen");
        return 1;
    }

    if (fgets(buf, bufSize, fp)) {
        // 去掉换行符
        buf[strcspn(buf, "\n")] = '\0';
    }
    pclose(fp);

    return 0;
}

/**
 * 检查一个IP地址及端口是否在名单中，不在则返回 -1
 */
int InNodeWhiteListCheck(char *client_ip, int client_port)
{

    // The whitelist mechanism does not take effect
    if (g_numNodeWhiteList <= 0) {
        return 0;
    }

    for (int i = 0;  i < g_numNodeWhiteList;  i++) {
        if ((strcmp(client_ip, g_pNodeWhiteList[i].ip) == 0) &&
            ((g_pNodeWhiteList[i].port <= 0) || (g_pNodeWhiteList[i].port == client_port)) &&
            (g_pNodeWhiteList[i].in_use == NODE_IN_USE) ) {
            return i;
        }
    }

    return -1;
}

static Config g_config;
Config *g_pconfig = &g_config;

#define CONCAT(a, b) a##b
#define STRINGIFY(x) #x
#define MACRO_EMBED_FMTSTR(x) "%" STRINGIFY(x) "[^,],%d,%u, %" STRINGIFY(x) "[^,],%d"

static void SetConfigId(const char *idStr)
{
    char buf[MAX_ID_SIZE];

    // 10 chars buffer for "Voter#"
    if (strlen(idStr) > sizeof(g_pconfig->id) - 10) {
        strZcpy(buf, idStr, MAX_ID_SIZE);
        idStr = buf;
    }

    /**
     * 可能作为 leveldb 的 key，这些关键字要与 InstanceID（8 字节整数编码） 区分开，所以长度
     * 必须大于8字节
     *                                                 123456789
     * _Voter_#{voterId}#{idTransaction}） | at least: _Voter_##, 9 characters
     * VResult#{voterId}#{idTransaction}   | at least: VResult##, 9 characters
     */
    snprintf(g_pconfig->id, sizeof(g_pconfig->id), "%s", idStr);
    snprintf(g_pconfig->idVoter, sizeof(g_pconfig->idVoter), "_Voter_#%s", idStr);
}


CFG_VOTER_WORKING_MODE g_cfg_Voter_Working_Mode[] = {
    { DIALOG_CLIENT_SERVER, "DIALOG_CLIENT_SERVER" },
    { ONE_IN_ONE_OUT_WITH_SYNC, "ONE_IN_ONE_OUT_WITH_SYNC" },
    { MORE_IN_ONE_OUT_AT_ONCE_WITH_SYNC, "MORE_IN_ONE_OUT_AT_ONCE_WITH_SYNC" },
    { MORE_IN_ONE_OUT_WITH_SYNC, "MORE_IN_ONE_OUT_WITH_SYNC" },
    { MORE_IN_ONE_OUT_PERIODIC_WITH_SYNC, "MORE_IN_ONE_OUT_PERIODIC_WITH_SYNC" },
    { MULTI_IN_MULTI_OUT_PERIODIC_WITH_SYNC, "MULTI_IN_MULTI_OUT_PERIODIC_WITH_SYNC" }
};

/**
 * ini_reader_handler
 */
int ini_reader_handler(void *user, const char *section, const char *name,
                                                              const char *value)
{
    (void)user;

    if (strcasecmp(section, "WhiteList") == 0) {
        if (g_numNodeWhiteList < MAX_SIZE_NodeWhiteList) {
           NodeWhiteList *lst = &g_pNodeWhiteList[g_numNodeWhiteList++];

            memset(lst, '\0', sizeof(NodeWhiteList));
            // sscanf如果不能读出要读到的变量的值，这些值就是它以前的值，所以这里不对其返回值做检查
            (void)sscanf(value, MACRO_EMBED_FMTSTR(INET_ADDRSTRLEN),
                                        lst->ip, &(lst->port), &(lst->in_use),
                                        lst->src_ip, &(lst->src_port));
            strZcpy(lst->id, name, sizeof(lst->id));

            printf("    [WhiteList] %s = IP: %s, PORT: %d, INUSE: %u\n",
                                         name, lst->ip, lst->port, lst->in_use);
        }
        else {
            syslog(LOG_ERR, "Too more element in WhiteList, MAX: %d",
                                                         MAX_SIZE_NodeWhiteList);
        }
    }
    else if (strcasecmp(section, "SERVER") == 0) {
        if (strcasecmp(name, "TCP_PORT") == 0) {
            g_iSERVER_PORT = atoi(value);

            printf("    [SERVER] TCP_PORT: %d\n", g_iSERVER_PORT);
        }
        else if (strcasecmp(name, "UDP_PORT") == 0) {
            g_iSERVER_PORT_UDP = atoi(value);

            printf("    [SERVER] UDP_PORT: %d\n", g_iSERVER_PORT_UDP);
        }
        else if (strcasecmp(name, "TCP_NAMESPACE") == 0) {
            strZcpy(g_iSERVER_NAMESPACE, value, sizeof(g_iSERVER_NAMESPACE));

            printf("    [SERVER] TCP_NAMESPACE: %s\n", g_iSERVER_NAMESPACE);
        }
        else if (strcasecmp(name, "UDP_NAMESPACE") == 0) {
            strZcpy(g_iSERVER_NAMESPACE_UDP, value, sizeof(g_iSERVER_NAMESPACE_UDP));

            printf("    [SERVER] UDP_NAMESPACE: %s\n", g_iSERVER_NAMESPACE_UDP);
        }
        else {
            syslog(LOG_ERR, "Bad config, In section, key: %s", name);
        }
    }
    else if (strcasecmp(section, "Voter") == 0) {
        if (strcasecmp(name, "numTransPerVotingcycle") == 0) {
            g_pconfig->numTransPerVotingcycle = atoi(value);

            // 校正不正确的config项目
            if (g_pconfig->numTransPerVotingcycle > MAX_NUM_TRANS) {
                g_pconfig->numTransPerVotingcycle = MAX_NUM_TRANS;
            }
            else if (g_pconfig->numTransPerVotingcycle <= 0) {
                g_pconfig->numTransPerVotingcycle = 1;
            }

            printf("    [Voter] numTransPerVotingcycle: %d\n",
                                             g_pconfig->numTransPerVotingcycle);
        }
        else if (strcasecmp(name, "Voter_Working_Mode") == 0) {
            int i;
            const int num = sizeof(g_cfg_Voter_Working_Mode) /
                                            sizeof(g_cfg_Voter_Working_Mode[0]);
            for (i = 0;  i < num;  i++) {
                if (strcasecmp(value, g_cfg_Voter_Working_Mode[i].configStr) == 0) {
                    g_Voter_Working_Mode = g_cfg_Voter_Working_Mode[i].workingMode;
                    printf("    [Voter] Voter_Working_Mode: %s", value);
                    break;
                }
            }

            if (i >= num) {
                g_Voter_Working_Mode = DIALOG_CLIENT_SERVER;
                syslog(LOG_ERR, "Bad config, [Voter], Bad Voter_Working_Mode: %s", value);
            }
        }
        else if (strcasecmp(name, "ID") == 0) {
            char hostname[HOST_NAME_MAX + 1];

            if ((strcasecmp(value, "$hostname") == 0) &&
                               (GetHostname(hostname, sizeof(hostname)) == 0)) {
                SetConfigId(hostname);
                printf("    [Voter] ID: %s\n", hostname);
            }
            else {
                SetConfigId(value);
                printf("    [Voter] ID: %s\n", value);
            }
        }
        else if (strcasecmp(name, "udp_data_in_raw_mode") == 0) {
            g_pconfig->udp_data_in_raw_mode = atoi(value);

            printf("    [Voter] udp_data_in_raw_mode: %'d\n",
                                               g_pconfig->udp_data_in_raw_mode);
        }
        else if (strcasecmp(name, "VOTING_CYCLE_NANOSECONDS") == 0) {
            g_pconfig->nanoSecondsVOTING_CYCLE = atol(value);

            printf("    [Voter] VOTING_CYCLE_NANOSECONDS: %'ld\n",
                                            g_pconfig->nanoSecondsVOTING_CYCLE);
        }
        else if (strcasecmp(name, "MAX_CLIENT_IDLE_TIME") == 0) {
            g_pconfig->max_CLIENT_IDLE_TIME = atol(value);

            printf("    [Voter] MAX_CLIENT_IDLE_TIME: %'ld\n",
                                               g_pconfig->max_CLIENT_IDLE_TIME);
        }
        else if (strcasecmp(name, "MINIMUM_VOTING_INTERVAL") == 0) {
            g_pconfig->minimum_VOTING_INTERVAL = atol(value);

            printf("    [Voter] MINIMUM_VOTING_INTERVAL: %'ld\n",
                                            g_pconfig->minimum_VOTING_INTERVAL);
        }
        else if (strcasecmp(name, "dl_param_sched_runtime") == 0) {
            g_pconfig->dl_param_sched_runtime = atol(value);

            printf("    [Voter] dl_param_sched_runtime: %'ld\n",
                                             g_pconfig->dl_param_sched_runtime);
        }
        else if (strcasecmp(name, "dl_param_sched_deadline") == 0) {
            g_pconfig->dl_param_sched_deadline = atol(value);

            printf("    [Voter] dl_param_sched_deadline: %'ld\n",
                                            g_pconfig->dl_param_sched_deadline);
        }
        else if (strcasecmp(name, "dl_param_sched_period") == 0) {
            g_pconfig->dl_param_sched_period = atol(value);

            printf("    [Voter] dl_param_sched_period: %'ld\n",
                                              g_pconfig->dl_param_sched_period);
        }
        else if (strcasecmp(name, "DataSynchronizationProtocol") == 0) {
            // 数据同步协议：paxos 或 ZMQ，缺省 ZMQ
            if (strcmp(value, "paxos") == 0) {
                g_pconfig->cDataSynchronizationProtocol = 'p';
            }
            else {
                g_pconfig->cDataSynchronizationProtocol = 'z';
            }

            printf("    [Voter] DataSynchronizationProtocol: %c\n",
                                       g_pconfig->cDataSynchronizationProtocol);
        }
        else if (strcasecmp(name, "OnlyMasterOutput") == 0) {
            g_pconfig->onlyMasterOutput = atoi(value);

            printf("    [Voter] OnlyMasterOutput: %c\n",
                                                   g_pconfig->onlyMasterOutput);
        }
        else if (strcasecmp(name, "minimum_VOTER_WORKING") == 0) {
            g_pconfig->minimum_VOTER_WORKING = atoi(value);

            printf("    [Voter] minimum_VOTER_WORKING: %c\n",
                                              g_pconfig->minimum_VOTER_WORKING);
        }
        else if (strcasecmp(name, "minimum_VOTING_PASS") == 0) {
            g_pconfig->minimum_VOTING_PASS = atoi(value);

            printf("    [Voter] minimum_VOTING_PASS: %c\n",
                                                g_pconfig->minimum_VOTING_PASS);
        }
        else if (strcasecmp(name, "numVoters") == 0) {
            g_pconfig->numVoters = atoi(value);
            if (g_pconfig->numVoters > MAX_NUM_VOTERS) {
                g_pconfig->numVoters = MAX_NUM_VOTERS;
            }

            printf("    [Voter] numVoters: %c\n", g_pconfig->numVoters);
        }
        else if (strcasecmp(name, "maxWait") == 0) {
            g_pconfig->maxWait = atoi(value);

            printf("    [Voter] maxWait: %c\n", g_pconfig->maxWait);
        }
    }
    else if (strcasecmp(section, "PhxPaxos") == 0) {
        if (strcasecmp(name, "IP_port") == 0) {
            strZcpy(g_pconfig->sIP_port, value, PATH_MAX);

            if ('\0' == g_pconfig->id[0]) {
                SetConfigId(value);
            }

            printf("    [PhxPaxos] IP_port: %s\n", g_pconfig->sIP_port);
        }
        else if (strcasecmp(name, "NodeIP_nodePort") == 0) {
            strncpy(g_pconfig->sNodeIP_nodePort, value, PATH_MAX * 10);
            g_pconfig->sNodeIP_nodePort[PATH_MAX * 10 - 1] = '\0';

            printf("    [PhxPaxos] NodeIP_nodePort: %s\n", g_pconfig->sNodeIP_nodePort);
        }
    }
    else if (strcasecmp(section, "OutputUDP") == 0) {
        if (g_numOutputNodeList < MAX_SIZE_OUTPUT_NodeList) {
            NodeWhiteList *lst = &g_pOutputNodeList[g_numOutputNodeList++];

            memset(lst, '\0', sizeof(NodeWhiteList));
            // sscanf如果不能读出要读到的变量的值，这些值就是它以前的值，所以这里不对其返回值做检查
            (void)sscanf(value, MACRO_EMBED_FMTSTR(INET_ADDRSTRLEN),
                                        lst->ip, &(lst->port), &(lst->in_use),
                                        lst->src_ip, &(lst->src_port));
            strZcpy(lst->id, name, sizeof(lst->id));

            printf("    [OutputUDP] %s = IP: %s, PORT: %d, INUSE: %d\n",
                                         name, lst->ip, lst->port, lst->in_use);
        }
        else {
            syslog(LOG_ERR, "Too more element in OutputUDP, MAX: %d",
                                                      MAX_SIZE_OUTPUT_NodeList);
        }
    }
    else if (strcasecmp(section, "DecisionMakingStrategy") == 0) {
        if (strcasecmp(name, "N_in_NooM") == 0) {
            g_pconfig->N_in_NooM = atoi(value);
            if ((g_pconfig->N_in_NooM < 0) || (g_pconfig->N_in_NooM > MIN_CLIENTS)) {
                g_pconfig->N_in_NooM  = MIN_CLIENTS;
            }
            printf("    [DecisionMakingStrategy] N_in_NooM = %d\n",
                                                             g_pconfig->N_in_NooM);
        }
        else if (strcasecmp(name, "M_in_NooM") == 0) {
            g_pconfig->M_in_NooM = atoi(value);
            if ((g_pconfig->M_in_NooM < 0) || (g_pconfig->M_in_NooM > MAX_CLIENTS)) {
                g_pconfig->M_in_NooM  = MAX_CLIENTS;
            }
            printf("    [DecisionMakingStrategy] M_in_NooM = %d\n",
                                                             g_pconfig->M_in_NooM);
        }
        else if (strcasecmp(name, "nodeArch") == 0) {
            if (g_pconfig->arch_total < MAX_ARCH_SUPPORT) {
                strZcpy(g_pconfig->nodeArches[g_pconfig->arch_total], value, MAX_ID_SIZE);
                printf("    [DecisionMakingStrategy] nodeArch(%d) = %s\n",
                       g_pconfig->arch_total, g_pconfig->nodeArches[g_pconfig->arch_total]);
                g_pconfig->arch_total++;
            }
            else {
                syslog(LOG_ERR, "Too more element in nodeArch, MAX: %d",
                                                              MAX_ARCH_SUPPORT);
            }
        }
        else if (strcasecmp(name, "WeaklyPeriodic") == 0) {
            g_pconfig->WeaklyPeriodic = atoi(value);
            printf("    [DecisionMakingStrategy] WeaklyPeriodic = %d\n",
                                                        g_pconfig->WeaklyPeriodic);
        }
        else if (strcasecmp(name, "StrongOrder") == 0) {
            g_pconfig->StrongOrder = atoi(value);
            printf("    [DecisionMakingStrategy] StrongOrder = %d\n",
                                                        g_pconfig->StrongOrder);
        }
    }
    else if (strcasecmp(section, "OS") == 0) {
        if (strcasecmp(name, "cmdLineSetNetwork") == 0) {
            strZcpy(g_pconfig->cmdLineSetNetwork[g_pconfig->numCmdLineSetNetwork],
                                                            value, MAX_ip_prefix_LEN);
            printf("    [OS] cmdLineSetNetwork: %s\n",
                        g_pconfig->cmdLineSetNetwork[g_pconfig->numCmdLineSetNetwork]);
            g_pconfig->numCmdLineSetNetwork++;
        }
        else if (strcasecmp(name, "cmdLineDelNetwork") == 0) {
            strZcpy(g_pconfig->cmdLineDelNetwork[g_pconfig->numcmdLineDelNetwork],
                                                            value, MAX_ip_prefix_LEN);
            printf("    [OS] cmdLineDelNetwork: %s\n",
                        g_pconfig->cmdLineDelNetwork[g_pconfig->numcmdLineDelNetwork]);
            g_pconfig->numcmdLineDelNetwork++;
        }
    }
    else {
        // syslog(LOG_ERR, "Bad config, Unknown section: [%s]", section);
    }

    return 1;
}

int TimespecToString(char *buffer, int bufferSize, struct timespec *ts)
{
    // 12345678901234567890123456789
    // 2017-06-29 11:21:03.790182200
    char buf[32];

    strftime(buf, sizeof(buf), "%F %T", gmtime(&(ts->tv_sec)));
    return snprintf(buffer, bufferSize, "%s.%09ld", buf, ts->tv_nsec);
}

void load_config_file_default()
{
    // PATH_MAX is defined in limits.h
    char path[PATH_MAX];
    char ini_path[PATH_MAX+8];

    // 清空配置变量
    memset(g_pconfig, '\0', sizeof(*g_pconfig));

    g_pconfig->N_in_NooM = MIN_CLIENTS;
    g_pconfig->M_in_NooM = MAX_CLIENTS;
    g_pconfig->nanoSecondsVOTING_CYCLE = VOTING_CYCLE_NANOSECONDS;

    g_pconfig->max_CLIENT_IDLE_TIME = MAX_CLIENT_IDLE_TIME;
    g_pconfig->cDataSynchronizationProtocol = 'z';
    g_pconfig->numVoters = 0;
    g_pconfig->minimum_VOTER_WORKING = 0;
    g_pconfig->minimum_VOTING_PASS = 0;

    g_pconfig->numCmdLineSetNetwork = 0;
    g_pconfig->numcmdLineDelNetwork = 0;

    g_pconfig->numTransPerVotingcycle = 1;

    // 预编译所有正则模式
    for (int i = 0;  i < g_pconfig->arch_total;  i++) {
        re_t reTmp = re_compile_me(g_pconfig->nodeArches[i],
                                   (re_t)&g_pconfig->archPatterns[i],
                                   (unsigned char *)&g_pconfig->g_ccl_buf[i]);
        if (NULL == reTmp) {
            printf("re_compile_me error: %s\n", g_pconfig->nodeArches[i]);
        }
    }

    // Get the path
    ssize_t length = readlink("/proc/self/exe", path, PATH_MAX - 1);

    if (-1 != length) {
        path[length] = '\0';  // Null-terminate the string
        snprintf(ini_path, sizeof(ini_path), "%s.ini", path);
    }
    else {
        snprintf(ini_path, sizeof(ini_path), "como_fscp_voter.ini");
    }

    printf("load_config_file: %s\n", ini_path);

    // Parse ini file
    int iRet = ini_parse(ini_path, ini_reader_handler, "");
    if (0 != iRet) {
        syslog(LOG_ERR, "Try to load config file '%s', failed.", ini_path);
    }
    else {
        syslog(LOG_ERR, "Try to load config file '%s', succeed.", ini_path);
    }
}

// 单独定义一个数据段，存放定期检测一些数据结构的完整性的数据
static MetadataWrapper comMetadata __attribute__ ((used,
                                           __section__(".como_fscp_voter"))) = {
    0, 0
};

// 指向 g_i64bit_Tokens 中某个数的下标，
int g_voterOutputTokenOrder = 0;
uint64_t g_i64bit_Tokens[] __attribute__((used, section(".como_fscp_voter"))) = {
    VOTER_TOKEN_0,
    VOTER_TOKEN_1,
    VOTER_TOKEN_2,
    VOTER_TOKEN_3,
    VOTER_TOKEN_4,
    VOTER_TOKEN_5,
    VOTER_TOKEN_6,
    VOTER_TOKEN_7,
    VOTER_TOKEN_8,
    VOTER_TOKEN_9
};
int g_numVoterOutputToken = sizeof(g_i64bit_Tokens) / sizeof(g_i64bit_Tokens[0]);

MetadataWrapper *g_pcomMetadata = &comMetadata;
